Programa de Pos graduação em Computação Aplicada – PPCA (UnB)
### Prova final de AEDI - Questão 2 ###
A questão 2 ira se basear na aplicação pratica de um problema de Ciencia de Dados em Machine Learning. Esses problemas são originados de dados reais aplicados em problemas de Negocios. A questão ira abordar a aplicação do problema Hotel Booking. Esse problema trata-se do objetivo de encontrar um modelo que seja capaz de prever se os indivıduos se cancelam ou não suas reservas em uma rede de hoteis. Com esses dados pede-se:
a) Elaborar uma analise descritiva da base de dados, analise grafica e por tabelas. (10%)
b) Realize a previsão por um modelo de Regressão Logıstica. (60%)
c) Encontre as features mais importantes para o cancelamento das reservas, interprete os resultados. (20%)
d) Explique o motivo do uso da regressão logıstica e não da regressão linear. (10%)
import statsmodels.api as sm
import pandas as pd
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import seaborn as sns
import plotly.express as px
sns.set(style="white")
sns.set(style="whitegrid", color_codes=True)
Hotel: são os hoteis que fezem parte da rede de hoteis.
is_canceled: è uma variavel dicotomica quantitativa para indicar se a reserva foi cancelada (1) ou não (0).
lead_time : è o numero de dias entre a data da reserva e o dia de chegada.
arrival_date_year : è o ano da data da chegada
arrival_date_month : è o mes da data da chegada
arrival_date_week_number : è o numero da semana no ano da data da chegada
arrival_date_day_of_month : numero do dia do mes da data da chegada
stays_in_weekend_nights : numero de dias reservadas no sabado e domingo
stays_in_week_nights : numero de dias reservadas nos dias da semana (seg-sex)
adults : numero de pessoas de maior idade
children : numero de crianças
babies : numero de bebès
meal : tipo de pacote com opçoes de reifeçoes
country : país de origem
market_segment : segmentação de mercado
distribution_channel : canal de distribuição como "TA" agencia de viagem ou "TO" operador turístico.
is_repeated_guest : indica se o usuario ja foi cliente do hotel, sim (1), não (0).
previous_cancellations : numero de cancelamentos de reservas anteriores
previous_bookings_not_canceled : numero de reservas anteriores não canceladas.
reserved_room_type : codigo do tipo de quarto reservado
assigned_room_type : codigo real do quarto atribuído ao usuário
booking_changes : numero de alterações de reserva atè o dia do check-in ou cancelamento
deposit_type : indica se teve pagamento adiantado pela reserva, pode assumir 3 formas:
pagamento total do valor sem rembolso (No_refund);
pagamento parcial do valor total com rembolso (Refundable);
não teve pagamento adiantado (No_deposit);
agent : codigo identificativo do operador turistico que fez a reserva
company : codigo identificativo da impresa que fez a reserva
days_in_waiting_list : numero de dias antes da reserva ser confirmada
customer_type : tipo de cliente
adr : custo medio diario das reservas
required_car_parking_spaces : numero de vaga para carro pedido do cliente
total_of_special_requests : numero de pedidos especiais
reservation_status : è o estato da reserva, pode assumir 3 estados:
check-out: o cliente fez o check-in e foi embora do hotel;
no-show: o cliente não fez o check-in mas informou o hotel da motivação;
canceled: a reserva foi cancelada pelo cliente.
reservation_status_date : data do ultimo estado da reserva, è util se for usado em conjunto com o reservation_status
Para que a regressão logistica seja realizada è preciso atender as seguintes condiçoes:
De acordo com a questão da prova, foi pedido para montar um modelo de previsão de cancelamento das reservas, portanto as possibilidade de cancelamento de uma reserva se pode pensar como uma variavel dicotomica, sim ou não, alias 1 ou 0 conforme oferecido no dataset.
A regressão logistica, diferentemente da regressão linear, aceita prever a probabilidade de ocorrência de um evento binário (sim/não) com base em um conjunto de variáveis independentes.
Outras diferenças de condiçoes entre regressão logistica e regressão linear são que essa ultima deve ter condiçoes de normalidade e de homocedasticidade.
Foi mostrada uma primeira visualização dos dados e da lista das variaveis.
Os dados nulos e duplicados foram limpos da base de dados.
Em primeira instancia foram descartadas as variaveis que achei poderiam não ser uteis ao objetivo, aquelas baseadas em avaliaçoes intuitivas ou por causa de incompatibilidade de tipo de dados.
Subsequentemente houve uma analise da correlação entre as variaveis por meio da heatmap e tambem foram descartadas algumas variaveis.
As variaveis categoricas e continuas foram subdivididas e organzadas por classes; a variavel y "is_canceled" foi definida como dependente.
Em seguida houve o tratamento, descrição dos dados e visualização. Nessa parte os dados foram organizados em tabelas. As variaveis categoricas foram comparadas cada uma com a variavel dependente. Tambem as variaveis continuas foram mantidas em cada tabela para observaçoes. Em conjunto com cada tabela foram plotados os graficos descritivos das frequencias subdivididas por zero e um; um tratamento especial dos dados foi aplicado as variaveis "country" e "agent" as quais foram selecionadas e plotadas de maneira diferente com o objetivo de selecionar as melhores 10 e criar sub-variaveis dummies
Em seguida foram plotados os histogramas e os boxplot para avaliar as distribuçoes de frequencias e a presença de outliers com analise visual.
Foram criadas mais variaveis dummies a partir das variaveis categoricas selecionadas no começo.
Foi necessario o rebalanceamento dos bancos de dados da variavel dependente porque as porcentagens foram bem maiores num lado do que o outro. O algoritmo SMOTE foi utilizado.
Em seguida as variaveis independentes foram reduzidas por meio do algoritmo RFE obtendo um número parcimonioso de variáveis mais importantes para o modelo.
Foi implementada a Regressão logistica (Logit) com o resultado dos coeficientes para cada variavel em ordem de importancia para o modelo.
Em seguida houve a avaliação da previsibilidade do modelo. A base de dados foi divididas em dados de treino e dados para test em busca da acurácia do modelo com base no teste.
A matriz de confusão foi utilizada para avaliar o numero das predições corretas na base de identificação de verdadeiros positivos, falsos positivos e falsos negativos.
Foram utlizadas as seguintes metricas, Precision, Recall e F1-score para clasificação do modelo. Enfim foi plotada a Curva ROC como metrica visual de avaliação do modelo.
Creando o dataframe do dataset e visualizando as variaveis de cada coluna.
data = pd.read_csv('hotel_bookings.csv', sep = ',', header = 0)
print(data.shape)
print(list(data.columns))
(119390, 32) ['hotel', 'is_canceled', 'lead_time', 'arrival_date_year', 'arrival_date_month', 'arrival_date_week_number', 'arrival_date_day_of_month', 'stays_in_weekend_nights', 'stays_in_week_nights', 'adults', 'children', 'babies', 'meal', 'country', 'market_segment', 'distribution_channel', 'is_repeated_guest', 'previous_cancellations', 'previous_bookings_not_canceled', 'reserved_room_type', 'assigned_room_type', 'booking_changes', 'deposit_type', 'agent', 'company', 'days_in_waiting_list', 'customer_type', 'adr', 'required_car_parking_spaces', 'total_of_special_requests', 'reservation_status', 'reservation_status_date']
Mostrando o dataset
data
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Resort Hotel | 0 | 342 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.00 | 0 | 0 | Check-Out | 2015-07-01 |
| 1 | Resort Hotel | 0 | 737 | 2015 | July | 27 | 1 | 0 | 0 | 2 | ... | No Deposit | NaN | NaN | 0 | Transient | 0.00 | 0 | 0 | Check-Out | 2015-07-01 |
| 2 | Resort Hotel | 0 | 7 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | NaN | NaN | 0 | Transient | 75.00 | 0 | 0 | Check-Out | 2015-07-02 |
| 3 | Resort Hotel | 0 | 13 | 2015 | July | 27 | 1 | 0 | 1 | 1 | ... | No Deposit | 304.0 | NaN | 0 | Transient | 75.00 | 0 | 0 | Check-Out | 2015-07-02 |
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.00 | 0 | 1 | Check-Out | 2015-07-03 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 119385 | City Hotel | 0 | 23 | 2017 | August | 35 | 30 | 2 | 5 | 2 | ... | No Deposit | 394.0 | NaN | 0 | Transient | 96.14 | 0 | 0 | Check-Out | 2017-09-06 |
| 119386 | City Hotel | 0 | 102 | 2017 | August | 35 | 31 | 2 | 5 | 3 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 225.43 | 0 | 2 | Check-Out | 2017-09-07 |
| 119387 | City Hotel | 0 | 34 | 2017 | August | 35 | 31 | 2 | 5 | 2 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 157.71 | 0 | 4 | Check-Out | 2017-09-07 |
| 119388 | City Hotel | 0 | 109 | 2017 | August | 35 | 31 | 2 | 5 | 2 | ... | No Deposit | 89.0 | NaN | 0 | Transient | 104.40 | 0 | 0 | Check-Out | 2017-09-07 |
| 119389 | City Hotel | 0 | 205 | 2017 | August | 35 | 29 | 2 | 7 | 2 | ... | No Deposit | 9.0 | NaN | 0 | Transient | 151.20 | 0 | 2 | Check-Out | 2017-09-07 |
119390 rows × 32 columns
Procurando linhas duplicadas e mostrandoas na tabela.
data.duplicated().value_counts()
False 87396 True 31994 Name: count, dtype: int64
data[data.duplicated(keep=False)]
| hotel | is_canceled | lead_time | arrival_date_year | arrival_date_month | arrival_date_week_number | arrival_date_day_of_month | stays_in_weekend_nights | stays_in_week_nights | adults | ... | deposit_type | agent | company | days_in_waiting_list | customer_type | adr | required_car_parking_spaces | total_of_special_requests | reservation_status | reservation_status_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.00 | 0 | 1 | Check-Out | 2015-07-03 |
| 5 | Resort Hotel | 0 | 14 | 2015 | July | 27 | 1 | 0 | 2 | 2 | ... | No Deposit | 240.0 | NaN | 0 | Transient | 98.00 | 0 | 1 | Check-Out | 2015-07-03 |
| 21 | Resort Hotel | 0 | 72 | 2015 | July | 27 | 1 | 2 | 4 | 2 | ... | No Deposit | 250.0 | NaN | 0 | Transient | 84.67 | 0 | 1 | Check-Out | 2015-07-07 |
| 22 | Resort Hotel | 0 | 72 | 2015 | July | 27 | 1 | 2 | 4 | 2 | ... | No Deposit | 250.0 | NaN | 0 | Transient | 84.67 | 0 | 1 | Check-Out | 2015-07-07 |
| 39 | Resort Hotel | 0 | 70 | 2015 | July | 27 | 2 | 2 | 3 | 2 | ... | No Deposit | 250.0 | NaN | 0 | Transient | 137.00 | 0 | 1 | Check-Out | 2015-07-07 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 119352 | City Hotel | 0 | 63 | 2017 | August | 35 | 31 | 0 | 3 | 3 | ... | No Deposit | 9.0 | NaN | 0 | Transient-Party | 195.33 | 0 | 2 | Check-Out | 2017-09-03 |
| 119353 | City Hotel | 0 | 63 | 2017 | August | 35 | 31 | 0 | 3 | 3 | ... | No Deposit | 9.0 | NaN | 0 | Transient-Party | 195.33 | 0 | 2 | Check-Out | 2017-09-03 |
| 119354 | City Hotel | 0 | 63 | 2017 | August | 35 | 31 | 0 | 3 | 3 | ... | No Deposit | 9.0 | NaN | 0 | Transient-Party | 195.33 | 0 | 2 | Check-Out | 2017-09-03 |
| 119372 | City Hotel | 0 | 175 | 2017 | August | 35 | 31 | 1 | 3 | 1 | ... | No Deposit | 42.0 | NaN | 0 | Transient | 82.35 | 0 | 1 | Check-Out | 2017-09-04 |
| 119373 | City Hotel | 0 | 175 | 2017 | August | 35 | 31 | 1 | 3 | 1 | ... | No Deposit | 42.0 | NaN | 0 | Transient | 82.35 | 0 | 1 | Check-Out | 2017-09-04 |
40165 rows × 32 columns
Eliminando as linhas duplicadas, 87396 linhas totais sobrando.
data = data.drop_duplicates()
data = data.reset_index(drop=True)
data.shape
(87396, 32)
#Procurando colunas com valores Nan
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
data.isnull().sum(axis = 0)
hotel 0 is_canceled 0 lead_time 0 arrival_date_year 0 arrival_date_month 0 arrival_date_week_number 0 arrival_date_day_of_month 0 stays_in_weekend_nights 0 stays_in_week_nights 0 adults 0 children 4 babies 0 meal 0 country 452 market_segment 0 distribution_channel 0 is_repeated_guest 0 previous_cancellations 0 previous_bookings_not_canceled 0 reserved_room_type 0 assigned_room_type 0 booking_changes 0 deposit_type 0 agent 12193 company 82137 days_in_waiting_list 0 customer_type 0 adr 0 required_car_parking_spaces 0 total_of_special_requests 0 reservation_status 0 reservation_status_date 0 dtype: int64
# Limpando os dados com Nan
data = data.drop(columns=['company'])
data[['country', 'agent']] = data[['country', 'agent']].fillna('not_defined')
data['children'] = data['children'].fillna(0)
#data.isnull().sum(axis = 0)
Escolhi de tirar do dataset a coluna 'company' pois tinha (82137) quasi a totalidade dos valors 'Nan'; As classes 'country' e 'agent' possuem uns valores aceitaveis de 'Nan' e foram substituidos com o atributo 'not_defined'; Enfim a variavel 'children' tinha so 4 valores "Nan" portanto foram substiduidos com zero.
Mostrando os valores únicos para cada variavel e os de tipo "objeto" pela identificação das variaveis categoricas.
# Convertindo reservation_status_date in datetime type
data['reservation_status_date'] = pd.to_datetime(data['reservation_status_date'])
data_uni_dty = pd.concat([d.reset_index(drop=False) for d in [data.nunique(), data.dtypes]], axis=1)
data_uni_dty = data_uni_dty.transpose()
data_uni_dty = data_uni_dty.drop_duplicates().transpose()
data_uni_dty.columns = ['variaveis','unique','dtype']
data_uni_dty
| variaveis | unique | dtype | |
|---|---|---|---|
| 0 | hotel | 2 | object |
| 1 | is_canceled | 2 | int64 |
| 2 | lead_time | 479 | int64 |
| 3 | arrival_date_year | 3 | int64 |
| 4 | arrival_date_month | 12 | object |
| 5 | arrival_date_week_number | 53 | int64 |
| 6 | arrival_date_day_of_month | 31 | int64 |
| 7 | stays_in_weekend_nights | 17 | int64 |
| 8 | stays_in_week_nights | 35 | int64 |
| 9 | adults | 14 | int64 |
| 10 | children | 5 | float64 |
| 11 | babies | 5 | int64 |
| 12 | meal | 5 | object |
| 13 | country | 178 | object |
| 14 | market_segment | 8 | object |
| 15 | distribution_channel | 5 | object |
| 16 | is_repeated_guest | 2 | int64 |
| 17 | previous_cancellations | 15 | int64 |
| 18 | previous_bookings_not_canceled | 73 | int64 |
| 19 | reserved_room_type | 10 | object |
| 20 | assigned_room_type | 12 | object |
| 21 | booking_changes | 21 | int64 |
| 22 | deposit_type | 3 | object |
| 23 | agent | 334 | object |
| 24 | days_in_waiting_list | 128 | int64 |
| 25 | customer_type | 4 | object |
| 26 | adr | 8879 | float64 |
| 27 | required_car_parking_spaces | 5 | int64 |
| 28 | total_of_special_requests | 6 | int64 |
| 29 | reservation_status | 3 | object |
| 30 | reservation_status_date | 926 | datetime64[ns] |
Visualizando os valores descritivos de cada variavel categorica
array_catg = []
for col in data.describe(include=['int64']):
if col == 'is_canceled' or col == 'is_repeated_guest':
print(col)
print(data[col].unique())
print('-'*60)
for col in data.describe(include=['object']):
print(col)
print(data[col].unique())
print('-'*60)
array_catg.append(col)
is_canceled [0 1] ------------------------------------------------------------ is_repeated_guest [0 1] ------------------------------------------------------------ hotel ['Resort Hotel' 'City Hotel'] ------------------------------------------------------------ arrival_date_month ['July' 'August' 'September' 'October' 'November' 'December' 'January' 'February' 'March' 'April' 'May' 'June'] ------------------------------------------------------------ meal ['BB' 'FB' 'HB' 'SC' 'Undefined'] ------------------------------------------------------------ country ['PRT' 'GBR' 'USA' 'ESP' 'IRL' 'FRA' 'not_defined' 'ROU' 'NOR' 'OMN' 'ARG' 'POL' 'DEU' 'BEL' 'CHE' 'CN' 'GRC' 'ITA' 'NLD' 'DNK' 'RUS' 'SWE' 'AUS' 'EST' 'CZE' 'BRA' 'FIN' 'MOZ' 'BWA' 'LUX' 'SVN' 'ALB' 'IND' 'CHN' 'MEX' 'MAR' 'UKR' 'SMR' 'LVA' 'PRI' 'SRB' 'CHL' 'AUT' 'BLR' 'LTU' 'TUR' 'ZAF' 'AGO' 'ISR' 'CYM' 'ZMB' 'CPV' 'ZWE' 'DZA' 'KOR' 'CRI' 'HUN' 'ARE' 'TUN' 'JAM' 'HRV' 'HKG' 'IRN' 'GEO' 'AND' 'GIB' 'URY' 'JEY' 'CAF' 'CYP' 'COL' 'GGY' 'KWT' 'NGA' 'MDV' 'VEN' 'SVK' 'FJI' 'KAZ' 'PAK' 'IDN' 'LBN' 'PHL' 'SEN' 'SYC' 'AZE' 'BHR' 'NZL' 'THA' 'DOM' 'MKD' 'MYS' 'ARM' 'JPN' 'LKA' 'CUB' 'CMR' 'BIH' 'MUS' 'COM' 'SUR' 'UGA' 'BGR' 'CIV' 'JOR' 'SYR' 'SGP' 'BDI' 'SAU' 'VNM' 'PLW' 'QAT' 'EGY' 'PER' 'MLT' 'MWI' 'ECU' 'MDG' 'ISL' 'UZB' 'NPL' 'BHS' 'MAC' 'TGO' 'TWN' 'DJI' 'STP' 'KNA' 'ETH' 'IRQ' 'HND' 'RWA' 'KHM' 'MCO' 'BGD' 'IMN' 'TJK' 'NIC' 'BEN' 'VGB' 'TZA' 'GAB' 'GHA' 'TMP' 'GLP' 'KEN' 'LIE' 'GNB' 'MNE' 'UMI' 'MYT' 'FRO' 'MMR' 'PAN' 'BFA' 'LBY' 'MLI' 'NAM' 'BOL' 'PRY' 'BRB' 'ABW' 'AIA' 'SLV' 'DMA' 'PYF' 'GUY' 'LCA' 'ATA' 'GTM' 'ASM' 'MRT' 'NCL' 'KIR' 'SDN' 'ATF' 'SLE' 'LAO'] ------------------------------------------------------------ market_segment ['Direct' 'Corporate' 'Online TA' 'Offline TA/TO' 'Complementary' 'Groups' 'Undefined' 'Aviation'] ------------------------------------------------------------ distribution_channel ['Direct' 'Corporate' 'TA/TO' 'Undefined' 'GDS'] ------------------------------------------------------------ reserved_room_type ['C' 'A' 'D' 'E' 'G' 'F' 'H' 'L' 'P' 'B'] ------------------------------------------------------------ assigned_room_type ['C' 'A' 'D' 'E' 'G' 'F' 'I' 'B' 'H' 'P' 'L' 'K'] ------------------------------------------------------------ deposit_type ['No Deposit' 'Refundable' 'Non Refund'] ------------------------------------------------------------ agent ['not_defined' 304.0 240.0 303.0 15.0 241.0 8.0 250.0 115.0 5.0 175.0 134.0 156.0 243.0 242.0 3.0 105.0 40.0 147.0 306.0 184.0 96.0 2.0 127.0 95.0 146.0 9.0 177.0 6.0 143.0 244.0 149.0 167.0 300.0 171.0 305.0 67.0 196.0 152.0 142.0 261.0 104.0 36.0 26.0 29.0 258.0 110.0 71.0 181.0 88.0 251.0 275.0 69.0 248.0 208.0 256.0 314.0 126.0 281.0 273.0 253.0 185.0 330.0 334.0 328.0 326.0 321.0 324.0 313.0 38.0 155.0 68.0 335.0 308.0 332.0 94.0 348.0 310.0 339.0 375.0 66.0 327.0 387.0 298.0 91.0 245.0 385.0 257.0 393.0 168.0 405.0 249.0 315.0 75.0 128.0 307.0 11.0 436.0 1.0 201.0 183.0 223.0 368.0 336.0 291.0 464.0 411.0 481.0 10.0 154.0 468.0 410.0 390.0 440.0 495.0 492.0 493.0 434.0 57.0 531.0 420.0 483.0 526.0 472.0 429.0 16.0 446.0 34.0 78.0 139.0 252.0 270.0 47.0 114.0 301.0 193.0 182.0 135.0 350.0 195.0 352.0 355.0 159.0 363.0 384.0 360.0 331.0 367.0 64.0 406.0 163.0 414.0 333.0 427.0 431.0 430.0 426.0 438.0 433.0 418.0 441.0 282.0 432.0 72.0 450.0 180.0 454.0 455.0 59.0 451.0 254.0 358.0 469.0 165.0 467.0 510.0 337.0 476.0 502.0 527.0 479.0 508.0 535.0 302.0 497.0 187.0 13.0 7.0 27.0 14.0 22.0 17.0 28.0 42.0 20.0 19.0 45.0 37.0 61.0 39.0 21.0 24.0 41.0 50.0 30.0 54.0 52.0 12.0 44.0 31.0 83.0 32.0 63.0 60.0 55.0 56.0 89.0 87.0 118.0 86.0 85.0 210.0 214.0 129.0 179.0 138.0 174.0 170.0 153.0 93.0 151.0 119.0 35.0 173.0 58.0 53.0 133.0 79.0 235.0 192.0 191.0 236.0 162.0 215.0 157.0 287.0 132.0 234.0 98.0 77.0 103.0 107.0 262.0 220.0 121.0 205.0 378.0 23.0 296.0 290.0 229.0 33.0 286.0 276.0 425.0 484.0 323.0 403.0 219.0 394.0 509.0 111.0 423.0 4.0 70.0 82.0 81.0 74.0 92.0 99.0 90.0 112.0 117.0 106.0 148.0 158.0 144.0 211.0 213.0 216.0 232.0 150.0 267.0 227.0 247.0 278.0 280.0 285.0 289.0 269.0 295.0 265.0 288.0 122.0 294.0 325.0 341.0 344.0 346.0 359.0 283.0 364.0 370.0 371.0 25.0 141.0 391.0 397.0 416.0 404.0 299.0 197.0 73.0 354.0 444.0 408.0 461.0 388.0 453.0 459.0 474.0 475.0 480.0 449.0] ------------------------------------------------------------ customer_type ['Transient' 'Contract' 'Transient-Party' 'Group'] ------------------------------------------------------------ reservation_status ['Check-Out' 'Canceled' 'No-Show'] ------------------------------------------------------------
Visualizando as variaveis continuas e colocando numa lista
cont_vars = []
for col in data.describe(include=['int64','float64','datetime64[ns]']):
if col == 'is_canceled' or col == 'is_repeated_guest':
continue
cont_vars.append(col)
cont_vars
['lead_time', 'arrival_date_year', 'arrival_date_week_number', 'arrival_date_day_of_month', 'stays_in_weekend_nights', 'stays_in_week_nights', 'adults', 'children', 'babies', 'previous_cancellations', 'previous_bookings_not_canceled', 'booking_changes', 'days_in_waiting_list', 'adr', 'required_car_parking_spaces', 'total_of_special_requests', 'reservation_status_date']
Procurando correlação entre as variaveis
#Heatmap with the correlation values
heat_data = data.copy()
heat_data = heat_data.filter(cont_vars + ['is_canceled'] + ['is_repeated_guest'])
plt.figure(figsize = (24, 12))
sns.heatmap(heat_data.corr(), annot=True)
<matplotlib.axes._subplots.AxesSubplot at 0x12c06da90>
Observando a heatmap è possivel observar que tem forte correlação entre as seguintes variaveis: "reservation_status_date" e "arrival_date_year".
Uma provavel correlação entre "is_repeated_guest" e "previous_bookings_not_canceled" pode fazer sentido.
Tambem entre "stays_in_weekend_nights" and "stays_in_week_nights" è provavel.
#Selecionando as variaveis continuas
to_remove = ["reservation_status_date", 'arrival_date_year','previous_bookings_not_canceled',
'stays_in_weekend_nights','stays_in_week_nights','arrival_date_week_number','arrival_date_day_of_month',]
sel_cont_vars = list(set(cont_vars) - set(to_remove))
sel_cont_vars
['total_of_special_requests', 'adr', 'children', 'required_car_parking_spaces', 'days_in_waiting_list', 'lead_time', 'babies', 'adults', 'booking_changes', 'previous_cancellations']
As variaveis, 'arrival_date_week_number','arrival_date_day_of_month' foram eliminadas pois não tem influencia no alcançamento do modelo.
Definendo 'is_canceled' como variavel dependente.
Procurando quantos cancelamentos foram feitos em porcentagem.
#Definendo variavel y 'is_canceled'
def_y_var = 'is_canceled'
data[def_y_var].value_counts()
is_canceled 0 63371 1 24025 Name: count, dtype: int64
figure = sns.countplot(x = def_y_var, data = data)
#plt.figure(figsize=(15,8))
plt.title("numero de cancelamentos")
plt.show()
res_no_canc = (data[def_y_var]==0).sum()
res_canc = (data[def_y_var]==1).sum()
pct_of_no_canc = res_no_canc/(res_no_canc+res_canc)
print("Percentual de pessoas que não cancelaram", round(pct_of_no_canc*100, 2), '%')
pct_of_canc = res_canc/(res_no_canc+res_canc)
print("Percentual de pessoas que cancelaram", round(pct_of_canc*100, 2), '%')
Percentual de pessoas que não cancelaram 72.51 % Percentual de pessoas que cancelaram 27.49 %
Observando as quantidades de cancelamentos por cadaum hotel.
data['hotel'].unique()
array(['Resort Hotel', 'City Hotel'], dtype=object)
data_hotel = data.copy()
data_hotel = data_hotel.filter(['hotel'] + [def_y_var] + sel_cont_vars)
data_hotel.groupby('hotel').mean()
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| hotel | |||||||||||
| City Hotel | 0.300386 | 0.710994 | 110.985944 | 0.131841 | 0.035618 | 1.020233 | 77.678521 | 0.007337 | 1.876338 | 0.246369 | 0.035768 |
| Resort Hotel | 0.234809 | 0.679021 | 99.025346 | 0.149317 | 0.160681 | 0.323834 | 83.371938 | 0.016309 | 1.874941 | 0.311293 | 0.021991 |
sns.countplot(x = 'hotel',hue = 'is_canceled', data = data)
plt.title("cancelamentos entres os hoteis")
plt.show()
Colocando a variavel de cancelamento 'is_canceled' em função das variaveis continuas selecionadas.
data_can = data.copy()
data_can = data_can.filter(['is_canceled'] + sel_cont_vars)
data_can.groupby('is_canceled').mean()
| total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|
| is_canceled | ||||||||||
| 0 | 0.760316 | 102.001961 | 0.119724 | 0.116157 | 0.722034 | 70.099588 | 0.012261 | 1.844235 | 0.313535 | 0.018715 |
| 1 | 0.535692 | 117.772476 | 0.188512 | 0.000000 | 0.822185 | 105.719251 | 0.007034 | 1.959043 | 0.160999 | 0.061270 |
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel is_repeated_guest
data_hotel = data.copy()
data_hotel = data_hotel.filter(['is_repeated_guest'] + [def_y_var] + sel_cont_vars)
data_hotel.groupby('is_repeated_guest').mean()
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| is_repeated_guest | |||||||||||
| 0 | 0.282969 | 0.698789 | 108.035027 | 0.142830 | 0.080137 | 0.775449 | 82.442255 | 0.011122 | 1.897489 | 0.270716 | 0.015051 |
| 1 | 0.076428 | 0.693119 | 64.585769 | 0.035432 | 0.184773 | 0.113031 | 17.160469 | 0.003514 | 1.342313 | 0.293411 | 0.408199 |
Plot da variavel is_canceled em função da variavel is_repeated_guest
sns.countplot(x = 'is_repeated_guest',hue = 'is_canceled', data = data)
plt.title("cancelamentos entres repeated guests")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel arrival_date_month
data_mon = data.copy()
data_mon = data_mon.filter(['arrival_date_month'] + [def_y_var] + sel_cont_vars)
data_mon.groupby('arrival_date_month').mean()
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| arrival_date_month | |||||||||||
| April | 0.304628 | 0.654148 | 103.612589 | 0.140238 | 0.079287 | 1.148710 | 75.855336 | 0.006702 | 1.884294 | 0.275923 | 0.017704 |
| August | 0.321844 | 0.835835 | 150.876120 | 0.243493 | 0.087679 | 0.195167 | 102.679133 | 0.017411 | 2.017234 | 0.297415 | 0.016967 |
| December | 0.268564 | 0.732021 | 81.450226 | 0.140324 | 0.096083 | 1.014812 | 58.997077 | 0.015202 | 1.845059 | 0.282206 | 0.052621 |
| February | 0.232043 | 0.638570 | 74.692033 | 0.126435 | 0.082158 | 0.197114 | 35.126927 | 0.009675 | 1.806658 | 0.237127 | 0.027222 |
| January | 0.221180 | 0.636054 | 70.050742 | 0.094183 | 0.103345 | 0.940337 | 34.463243 | 0.010228 | 1.728319 | 0.287236 | 0.082250 |
| July | 0.317987 | 0.767625 | 135.542014 | 0.227603 | 0.086010 | 0.098538 | 111.960127 | 0.011932 | 2.009844 | 0.251069 | 0.033310 |
| June | 0.303155 | 0.688345 | 119.750120 | 0.131616 | 0.078429 | 0.496845 | 103.982743 | 0.009015 | 1.898519 | 0.252672 | 0.018158 |
| March | 0.243578 | 0.573939 | 81.609523 | 0.090643 | 0.078264 | 0.390523 | 53.409024 | 0.007587 | 1.810196 | 0.254625 | 0.014242 |
| May | 0.292280 | 0.665230 | 111.195703 | 0.098983 | 0.072172 | 1.261041 | 92.169240 | 0.008618 | 1.850389 | 0.277080 | 0.015320 |
| November | 0.211011 | 0.707508 | 72.754460 | 0.052853 | 0.088889 | 0.647447 | 47.582983 | 0.008809 | 1.703904 | 0.298498 | 0.033634 |
| October | 0.236804 | 0.678685 | 90.152518 | 0.096337 | 0.088693 | 1.698731 | 82.922123 | 0.008941 | 1.824776 | 0.274301 | 0.041246 |
| September | 0.245441 | 0.696562 | 112.081263 | 0.086996 | 0.081315 | 1.505830 | 94.230942 | 0.013004 | 1.876532 | 0.277578 | 0.050822 |
Plot da variavel is_canceled em função da variavel arrival_date_month
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'arrival_date_month',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()
data['country'].value_counts()[:10].reset_index()
| country | count | |
|---|---|---|
| 0 | PRT | 27453 |
| 1 | GBR | 10433 |
| 2 | FRA | 8837 |
| 3 | ESP | 7252 |
| 4 | DEU | 5387 |
| 5 | ITA | 3066 |
| 6 | IRL | 3016 |
| 7 | BEL | 2081 |
| 8 | BRA | 1995 |
| 9 | NLD | 1911 |
Paises com mais frequencias de cancelamentos
canceled_data = data[data['is_canceled'] == 1]
top_10_country = canceled_data['country'].value_counts()[:10].reset_index()
top_10_country.columns = ['country','dados']
print(top_10_country)
fig = px.pie(top_10_country,names='country',values='dados',title='Top 10 paises com mais frequencias de cancelamentos',template='simple_white')
fig.update_traces(textposition='inside',textinfo='label+value+percent')
fig.show()
country dados 0 PRT 9791 1 GBR 1985 2 ESP 1862 3 FRA 1733 4 ITA 1075 5 DEU 1053 6 BRA 727 7 IRL 668 8 USA 459 9 BEL 411
Selecionando os top 10 paises com maior numero de reservas e com mais cancelamentos em porcentagem.
data_cou = data.copy()
data_cou = data_cou.filter(['country'] + [def_y_var] + sel_cont_vars)
data_cou['counter'] = data_cou.groupby('country')['country'].transform('count')
data_cou.groupby('country').mean()
data_cou2 = data_cou.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_cou2 = data_cou2.groupby('country').mean()
sorted_data_cou = data_cou2.sort_values(by=['counter'], ascending=False).head(10)
sorted_data_cou2 = sorted_data_cou.sort_values(by=['is_canceled'], ascending=False).head(10)
#sorted_data_cou2 = sorted_data_cou2.where(sorted_data_cou2['counter'] > 10, np.nan)
#sorted_data_cou2 = sorted_data_cou2.dropna()
print(sorted_data_cou2.shape)
sorted_data_cou2
#sorted_data_cou2.plot(x='country', y='counter')
(10, 11)
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| country | |||||||||||
| BRA | 1995.0 | 0.364411 | 0.853634 | 112.726937 | 0.189474 | 0.069674 | 0.204010 | 80.864662 | 0.012030 | 2.003008 | 0.279699 |
| PRT | 27453.0 | 0.356646 | 0.594252 | 95.839512 | 0.124176 | 0.111318 | 1.279678 | 65.101228 | 0.015845 | 1.759371 | 0.268167 |
| ITA | 3066.0 | 0.350620 | 0.691455 | 116.064018 | 0.145140 | 0.041422 | 0.961840 | 83.231246 | 0.005219 | 1.970646 | 0.278865 |
| ESP | 7252.0 | 0.256757 | 0.769029 | 122.283610 | 0.190017 | 0.162300 | 0.149890 | 52.196773 | 0.017375 | 1.954909 | 0.267788 |
| IRL | 3016.0 | 0.221485 | 0.810676 | 100.523140 | 0.101790 | 0.027851 | 0.043103 | 114.276857 | 0.007958 | 1.957560 | 0.266247 |
| BEL | 2081.0 | 0.197501 | 0.865449 | 115.379188 | 0.144642 | 0.063912 | 0.119173 | 94.290245 | 0.005766 | 1.962037 | 0.233061 |
| FRA | 8837.0 | 0.196107 | 0.765984 | 112.531958 | 0.134661 | 0.064049 | 0.846667 | 74.135906 | 0.008713 | 1.967636 | 0.236958 |
| DEU | 5387.0 | 0.195471 | 0.696120 | 105.939399 | 0.087990 | 0.045109 | 1.051977 | 105.089103 | 0.003341 | 1.915166 | 0.230555 |
| GBR | 10433.0 | 0.190262 | 0.700182 | 97.669062 | 0.115978 | 0.052621 | 0.377264 | 117.419055 | 0.008818 | 1.924950 | 0.293013 |
| NLD | 1911.0 | 0.183150 | 0.796442 | 109.204359 | 0.145474 | 0.094715 | 0.358974 | 79.920984 | 0.005233 | 1.893250 | 0.299843 |
O Portugal apesar de não ser clasificado como primeiro em relação aos numero de cancelamento è o maior pais em termos de frequecias. O Brasil è primeiro em termos de porcentagem de cancelamentos. A Hollanda entra entre os primeiros 10 paises em vez do que os EUA.
sorted_data_cou2_plot = sorted_data_cou2.reset_index()
fig = px.histogram(sorted_data_cou2_plot, x="country", y="is_canceled", title='Top 10 paises com mais cancelamentos em porcentagem')
fig.show()
Criando as variaveis dummies para os top10 paises em porcentagem de cancelamento
first_countries = []
for row in sorted_data_cou2.index:
first_countries.append('country_'+row)
data_cou = data.copy()
cat_vars_cou =['country']
for var in cat_vars_cou:
cat_list='var'+'_'+var
cat_list = pd.get_dummies(data_cou[var], prefix=var, dtype=int)
data1=data_cou.join(cat_list)
data_cou=data1
filtered_data_cou = data_cou.filter(first_countries)
filtered_data_cou
| country_BRA | country_PRT | country_ITA | country_ESP | country_IRL | country_BEL | country_FRA | country_DEU | country_GBR | country_NLD | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 87391 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 87392 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 87393 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 87394 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 87395 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
87396 rows × 10 columns
Separando os dados de cancelamento para cada pais selecionado
filtered_data_cou_chart = pd.concat([data['is_canceled'], filtered_data_cou], axis=1)
filtered_data_cou_chart = filtered_data_cou_chart.groupby('is_canceled').sum()
#filtered_data_cou_chart = filtered_data_cou_chart.reset_index()
filtered_data_cou_chart
| country_BRA | country_PRT | country_ITA | country_ESP | country_IRL | country_BEL | country_FRA | country_DEU | country_GBR | country_NLD | |
|---|---|---|---|---|---|---|---|---|---|---|
| is_canceled | ||||||||||
| 0 | 1268 | 17662 | 1991 | 5390 | 2348 | 1670 | 7104 | 4334 | 8448 | 1561 |
| 1 | 727 | 9791 | 1075 | 1862 | 668 | 411 | 1733 | 1053 | 1985 | 350 |
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel meal
data_mea = data.copy()
data_mea = data_mea.filter(['meal'] + [def_y_var] + sel_cont_vars)
data_mea1 = data_mea.groupby('meal').mean()
data_mea1.sort_values(by=['is_canceled'], ascending=False)
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| meal | |||||||||||
| SC | 0.353022 | 0.755406 | 98.278183 | 0.018880 | 0.024892 | 0.036389 | 66.255247 | 0.003903 | 1.849699 | 0.184158 | 0.011602 |
| FB | 0.275000 | 0.505556 | 143.293667 | 0.213889 | 0.152778 | 0.091667 | 97.580556 | 0.066667 | 1.994444 | 0.636111 | 0.241667 |
| HB | 0.269675 | 0.708971 | 133.272635 | 0.189323 | 0.106769 | 1.701266 | 114.984920 | 0.019923 | 1.970941 | 0.427958 | 0.022675 |
| BB | 0.265483 | 0.692709 | 103.669533 | 0.148342 | 0.089088 | 0.693680 | 76.942790 | 0.010136 | 1.866810 | 0.258422 | 0.033011 |
| Undefined | 0.166667 | 0.361789 | 105.811850 | 0.113821 | 0.089431 | 5.121951 | 89.097561 | 0.030488 | 1.776423 | 0.623984 | 0.022358 |
Plot da variavel is_canceled em função da variavel meal
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'meal',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel market_segment
data_mseg = data.copy()
data_mseg = data_mseg.filter(['market_segment'] + [def_y_var] + sel_cont_vars)
data_mseg['counter'] = data_mseg.groupby('market_segment')['market_segment'].transform('count')
data_mseg.groupby('market_segment').mean()
data_mseg2 = data_mseg.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_mseg2 = data_mseg2.groupby('market_segment').mean()
sorted_data_mseg = data_mseg2.sort_values(by=['counter'], ascending=False)
sorted_data_mseg2 = sorted_data_mseg.sort_values(by=['is_canceled'], ascending=False)
sorted_data_mseg2
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| market_segment | |||||||||||
| Undefined | 2.0 | 1.000000 | 1.500000 | 15.000000 | 0.000000 | 0.000000 | 0.000000 | 1.500000 | 0.000000 | 2.500000 | 0.000000 |
| Online TA | 51618.0 | 0.353462 | 0.903832 | 118.171606 | 0.176063 | 0.073521 | 0.002499 | 79.902805 | 0.008330 | 1.961196 | 0.208183 |
| Groups | 4942.0 | 0.270134 | 0.163092 | 74.864284 | 0.011736 | 0.052206 | 6.803116 | 147.605423 | 0.002631 | 1.656212 | 0.636180 |
| Aviation | 227.0 | 0.198238 | 0.118943 | 100.170396 | 0.000000 | 0.026432 | 0.000000 | 4.281938 | 0.000000 | 1.008811 | 0.255507 |
| Offline TA/TO | 13889.0 | 0.148535 | 0.351933 | 81.764191 | 0.046008 | 0.039456 | 2.208222 | 106.052416 | 0.012672 | 1.865793 | 0.230542 |
| Direct | 11804.0 | 0.147154 | 0.580312 | 116.579429 | 0.187987 | 0.177652 | 0.043290 | 48.825059 | 0.024483 | 1.883514 | 0.410200 |
| Complementary | 702.0 | 0.125356 | 0.958689 | 3.049245 | 0.086895 | 0.115385 | 0.055556 | 13.635328 | 0.031339 | 1.504274 | 0.336182 |
| Corporate | 4212.0 | 0.121083 | 0.273267 | 68.151246 | 0.012108 | 0.136752 | 0.127968 | 16.252849 | 0.003799 | 1.206553 | 0.358262 |
Plot da variavel is_canceled em função da variavel market_segment
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'market_segment',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel distribution_channel
data_dch = data.copy()
data_dch = data_dch.filter(['distribution_channel'] + [def_y_var] + sel_cont_vars)
data_dch['counter'] = data_dch.groupby('distribution_channel')['distribution_channel'].transform('count')
data_dch.groupby('distribution_channel').mean()
data_dch2 = data_dch.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_dch2 = data_dch2.groupby('distribution_channel').mean()
sorted_data_dch = data_dch2.sort_values(by=['counter'], ascending=False)
sorted_data_dch2 = sorted_data_dch.sort_values(by=['is_canceled'], ascending=False)
sorted_data_dch2
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| distribution_channel | |||||||||||
| Undefined | 5.0 | 0.800000 | 1.400000 | 46.240000 | 0.200000 | 0.200000 | 0.000000 | 23.000000 | 0.000000 | 2.200000 | 0.000000 |
| TA/TO | 69141.0 | 0.309686 | 0.756845 | 108.559116 | 0.141493 | 0.064564 | 0.863930 | 88.638478 | 0.008866 | 1.927409 | 0.231455 |
| GDS | 181.0 | 0.198895 | 0.204420 | 120.317845 | 0.000000 | 0.000000 | 0.000000 | 20.121547 | 0.000000 | 1.088398 | 0.099448 |
| Direct | 12988.0 | 0.148214 | 0.568140 | 109.133604 | 0.175162 | 0.170927 | 0.368263 | 52.362796 | 0.024022 | 1.853172 | 0.426548 |
| Corporate | 5081.0 | 0.127534 | 0.255855 | 68.515682 | 0.011218 | 0.133045 | 0.195434 | 33.416257 | 0.004133 | 1.259004 | 0.428262 |
Plot da variavel is_canceled em função da variavel distribution_channel
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'distribution_channel',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel reserved_room_type
data_rrt = data.copy()
data_rrt = data_rrt.filter(['reserved_room_type'] + [def_y_var] + sel_cont_vars)
data_rrt.groupby('reserved_room_type').mean()
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| reserved_room_type | |||||||||||
| A | 0.259708 | 0.650286 | 92.297814 | 0.050237 | 0.068839 | 1.064206 | 78.111278 | 0.008541 | 1.774933 | 0.266852 | 0.038460 |
| B | 0.318318 | 0.794795 | 90.377878 | 0.607608 | 0.043043 | 0.139139 | 113.345345 | 0.014014 | 1.527528 | 0.561562 | 0.036036 |
| C | 0.323497 | 0.784699 | 160.561770 | 1.288525 | 0.193443 | 0.156284 | 78.761749 | 0.072131 | 2.031694 | 0.495082 | 0.008743 |
| D | 0.300954 | 0.798770 | 122.077545 | 0.043798 | 0.074089 | 0.169100 | 82.902518 | 0.010346 | 2.105932 | 0.227612 | 0.013852 |
| E | 0.272607 | 0.860307 | 125.936130 | 0.098033 | 0.165647 | 0.322367 | 88.297074 | 0.013721 | 1.985617 | 0.299223 | 0.018020 |
| F | 0.301452 | 0.737159 | 168.272310 | 1.038257 | 0.134254 | 0.000000 | 68.243712 | 0.016295 | 1.993978 | 0.342898 | 0.012398 |
| G | 0.359162 | 0.656920 | 176.727904 | 1.276803 | 0.199805 | 0.074074 | 79.838207 | 0.031676 | 2.078947 | 0.340156 | 0.024366 |
| H | 0.407718 | 0.394295 | 188.763993 | 0.978188 | 0.281879 | 0.000000 | 78.206376 | 0.015101 | 2.714765 | 0.328859 | 0.005034 |
| L | 0.333333 | 0.000000 | 124.666667 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 2.166667 | 0.000000 | 0.166667 |
| P | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
Plot da variavel is_canceled em função da variavel reserved_room_type
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'reserved_room_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel assigned_room_type
data_art = data.copy()
data_art = data_art.filter(['assigned_room_type'] + [def_y_var] + sel_cont_vars)
data_art.groupby('assigned_room_type').mean()
| is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | previous_cancellations | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| assigned_room_type | |||||||||||
| A | 0.306221 | 0.663960 | 95.849738 | 0.045862 | 0.056248 | 0.980977 | 83.338523 | 0.007233 | 1.785525 | 0.238637 | 0.042213 |
| B | 0.219231 | 0.681319 | 95.368533 | 0.323626 | 0.056593 | 2.519780 | 91.806593 | 0.015934 | 1.633516 | 0.495604 | 0.026923 |
| C | 0.182448 | 0.688684 | 116.900878 | 0.573210 | 0.166744 | 0.692379 | 82.182448 | 0.055427 | 1.972748 | 0.520092 | 0.008776 |
| D | 0.242109 | 0.738766 | 109.067211 | 0.054297 | 0.084388 | 0.372013 | 73.857525 | 0.009763 | 2.004012 | 0.243313 | 0.015424 |
| E | 0.235997 | 0.812370 | 119.199101 | 0.087978 | 0.150382 | 0.423767 | 83.172620 | 0.013482 | 1.951216 | 0.299792 | 0.021265 |
| F | 0.246485 | 0.754067 | 152.870074 | 0.793218 | 0.148883 | 0.337745 | 67.588089 | 0.014888 | 1.963055 | 0.344913 | 0.019024 |
| G | 0.301441 | 0.684548 | 167.424868 | 1.111289 | 0.201761 | 0.311849 | 75.607686 | 0.030424 | 2.041233 | 0.361890 | 0.022418 |
| H | 0.352691 | 0.439093 | 171.881530 | 0.837110 | 0.271955 | 0.000000 | 74.810198 | 0.018414 | 2.567989 | 0.338527 | 0.009915 |
| I | 0.014006 | 0.605042 | 40.801933 | 0.151261 | 0.190476 | 0.890756 | 66.733894 | 0.005602 | 1.801120 | 0.910364 | 0.002801 |
| K | 0.043478 | 0.663043 | 53.829674 | 0.047101 | 0.047101 | 1.000000 | 42.271739 | 0.003623 | 1.195652 | 1.170290 | 0.007246 |
| L | 1.000000 | 0.000000 | 8.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 2.000000 | 0.000000 | 1.000000 |
| P | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
Plot da variavel is_canceled em função da variavel assigned_room_type
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'assigned_room_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por assigned_room_type")
plt.show()
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel deposit_type
data_dep = data.copy()
data_dep = data_dep.filter(['deposit_type'] + [def_y_var] + sel_cont_vars)
data_dep['counter'] = data_dep.groupby('deposit_type')['deposit_type'].transform('count')
data_dep.groupby('deposit_type').mean()
data_dep2 = data_dep.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_dep2.groupby('deposit_type').mean()
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| deposit_type | |||||||||||
| No Deposit | 86251.0 | 0.266849 | 0.707400 | 106.537882 | 0.140311 | 0.085100 | 0.617732 | 78.228600 | 0.010968 | 1.877764 | 0.273249 |
| Non Refund | 1038.0 | 0.947013 | 0.016378 | 92.388073 | 0.008671 | 0.000963 | 11.007707 | 211.304432 | 0.000000 | 1.712909 | 0.085742 |
| Refundable | 107.0 | 0.242991 | 0.196262 | 79.928318 | 0.046729 | 0.186916 | 7.504673 | 145.392523 | 0.000000 | 1.869159 | 0.747664 |
Plot da variavel is_canceled em função da variavel deposit_type
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'deposit_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por deposit_type")
plt.show()
data['agent'].value_counts()[:10].reset_index()
| agent | count | |
|---|---|---|
| 0 | 9.0 | 28759 |
| 1 | 240.0 | 13028 |
| 2 | not_defined | 12193 |
| 3 | 14.0 | 3349 |
| 4 | 7.0 | 3300 |
| 5 | 250.0 | 2779 |
| 6 | 241.0 | 1644 |
| 7 | 28.0 | 1502 |
| 8 | 8.0 | 1383 |
| 9 | 1.0 | 1232 |
Agentes com mais frequencias de cancelamentos
canceled_data = data[data['is_canceled'] == 1]
top_10_agent = canceled_data['agent'].value_counts()[:10].reset_index()
top_10_agent.columns = ['agent','dados']
print(top_10_agent)
fig = px.pie(top_10_agent,names='agent',values='dados',title='Top 10 agentes com mais frequencias de cancelamentos',template='simple_white')
fig.update_traces(textposition='inside',textinfo='label+value+percent')
fig.show()
agent dados 0 9.0 11524 1 240.0 4944 2 not_defined 1557 3 14.0 583 4 250.0 494 5 1.0 458 6 7.0 436 7 8.0 385 8 242.0 236 9 241.0 218
Selecionando os top 10 agentes com maior numero de reservas e com mais cancelamentos em porcentagem.
data_agn = data.copy()
data_agn = data_agn.filter(['agent'] + [def_y_var] + sel_cont_vars)
data_agn['counter'] = data_agn.groupby('agent')['agent'].transform('count')
data_agn.groupby('agent').mean()
data_agn2 = data_agn.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_agn2 = data_agn2.groupby('agent').mean()
sorted_data_agn = data_agn2.sort_values(by=['counter'], ascending=False).head(10)
sorted_data_agn2 = sorted_data_agn.sort_values(by=['is_canceled'], ascending=False).head(10)
print(sorted_data_agn2.shape)
sorted_data_agn2
(10, 11)
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| agent | |||||||||||
| 9.0 | 28759.0 | 0.400709 | 0.910602 | 123.540350 | 0.161132 | 0.037310 | 0.000000 | 81.949963 | 0.005459 | 2.000661 | 0.209708 |
| 240.0 | 13028.0 | 0.379490 | 0.990636 | 117.514454 | 0.222597 | 0.165029 | 0.000000 | 82.682683 | 0.015121 | 1.940897 | 0.235263 |
| 1.0 | 1232.0 | 0.371753 | 0.148539 | 75.048109 | 0.008929 | 0.001623 | 10.518669 | 148.228084 | 0.001623 | 1.686688 | 0.333604 |
| 8.0 | 1383.0 | 0.278380 | 0.791757 | 111.346074 | 0.151121 | 0.038322 | 0.064353 | 86.064353 | 0.005061 | 1.868402 | 0.109906 |
| 250.0 | 2779.0 | 0.177762 | 0.639798 | 133.727280 | 0.253329 | 0.274559 | 0.000000 | 70.381792 | 0.037064 | 1.991364 | 0.546240 |
| 14.0 | 3349.0 | 0.174082 | 0.577486 | 127.307961 | 0.223649 | 0.085399 | 0.002986 | 67.349358 | 0.018812 | 1.888922 | 0.373544 |
| 241.0 | 1644.0 | 0.132603 | 1.055961 | 101.855426 | 0.181265 | 0.147810 | 0.000000 | 70.793187 | 0.014599 | 1.967762 | 0.122263 |
| 7.0 | 3300.0 | 0.132121 | 0.936364 | 96.659303 | 0.151212 | 0.006970 | 0.000000 | 68.678485 | 0.004242 | 1.873939 | 0.102121 |
| not_defined | 12193.0 | 0.127696 | 0.447306 | 82.984567 | 0.076273 | 0.157303 | 0.383909 | 37.045436 | 0.012876 | 1.579267 | 0.416632 |
| 28.0 | 1502.0 | 0.057923 | 0.225033 | 79.714281 | 0.045273 | 0.003329 | 0.000666 | 58.679095 | 0.011984 | 2.006658 | 0.076565 |
Na lista dos top 10 agentes o agente com id 28 entrou nos primeiros 10 agentes no lugar do agente com id 242. Essa foi a unica mudança.
#Plotando as porcentagem de cancelamento para cada agente
sorted_data_agn2_plot = sorted_data_agn2.reset_index()
sorted_data_agn2_plot['agent'] = sorted_data_agn2_plot['agent'].astype("string")
fig = px.histogram(sorted_data_agn2_plot, x="agent", y="is_canceled", title='Top 10 agentes com mais cancelamentos em porcentagem')
fig.show()
Criando as variaveis dummies
first_agents = []
for row in sorted_data_agn2.index:
first_agents.append('agent_'+ str(row))
data_agn = data.copy()
cat_vars_cou =['agent']
for var in cat_vars_cou:
cat_list='var'+'_'+var
cat_list = pd.get_dummies(data_cou[var], prefix=var, dtype=int)
data1=data_agn.join(cat_list)
data_agn=data1
filtered_data_agn = data_agn.filter(first_agents)
filtered_data_agn
| agent_9.0 | agent_240.0 | agent_1.0 | agent_8.0 | agent_250.0 | agent_14.0 | agent_241.0 | agent_7.0 | agent_not_defined | agent_28.0 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 87391 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 87392 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 87393 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 87394 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 87395 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
87396 rows × 10 columns
Separando os dados de cancelamento para cada agente selecionado
filtered_data_agn_chart = pd.concat([data['is_canceled'], filtered_data_agn], axis=1)
filtered_data_agn_chart.shape
filtered_data_agn_chart = filtered_data_agn_chart.groupby('is_canceled').sum()
#filtered_data_agn_chart = filtered_data_agn_chart.reset_index()
filtered_data_agn_chart
| agent_9.0 | agent_240.0 | agent_1.0 | agent_8.0 | agent_250.0 | agent_14.0 | agent_241.0 | agent_7.0 | agent_not_defined | agent_28.0 | |
|---|---|---|---|---|---|---|---|---|---|---|
| is_canceled | ||||||||||
| 0 | 17235 | 8084 | 774 | 998 | 2285 | 2766 | 1426 | 2864 | 10636 | 1415 |
| 1 | 11524 | 4944 | 458 | 385 | 494 | 583 | 218 | 436 | 1557 | 87 |
Observando a variavel is_canceled e as variaveis cotinuas em função da variavel customer_type
data_cus = data.copy()
data_cus = data_cus.filter(['customer_type'] + [def_y_var] + sel_cont_vars)
data_cus['counter'] = data_cus.groupby('customer_type')['customer_type'].transform('count')
data_cus.groupby('customer_type').mean()
data_cus2 = data_cus.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_cus2 = data_cus2.groupby('customer_type').mean()
sorted_data_cus = data_cus2.sort_values(by=['counter'], ascending=False)
sorted_data_cus2 = sorted_data_cus.sort_values(by=['is_canceled'], ascending=False)
print(sorted_data_cus2.shape)
sorted_data_cus2
(4, 11)
| counter | is_canceled | total_of_special_requests | adr | children | required_car_parking_spaces | days_in_waiting_list | lead_time | babies | adults | booking_changes | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| customer_type | |||||||||||
| Transient | 71986.0 | 0.301059 | 0.732073 | 110.135859 | 0.153669 | 0.088739 | 0.177896 | 73.427236 | 0.011377 | 1.903398 | 0.224238 |
| Contract | 3139.0 | 0.163109 | 0.838484 | 92.753036 | 0.083785 | 0.042052 | 0.019433 | 109.225231 | 0.010194 | 1.909525 | 0.150048 |
| Transient-Party | 11727.0 | 0.152383 | 0.457918 | 87.675056 | 0.064211 | 0.067366 | 4.470794 | 113.032574 | 0.007675 | 1.673744 | 0.593417 |
| Group | 544.0 | 0.099265 | 0.645221 | 84.361949 | 0.069853 | 0.093750 | 0.391544 | 51.584559 | 0.009191 | 2.384191 | 0.303309 |
Plot da variavel is_canceled em função da variavel customer_type
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'customer_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por deposit_type")
plt.show()
# Plotando as distribuçoes de frequencias
cont_data_plot = sel_cont_vars
#data_canc = data[data['is_canceled'] == 1]
fig, axarr = plt.subplots(5, 2, figsize=(10, 30))
for s in range(0, 5):
sns.histplot(x=cont_data_plot[s], data = data, ax=axarr[s,0])
for s in range(5, len(cont_data_plot)):
sns.histplot(x=cont_data_plot[s], data = data, ax=axarr[s-5,1])
# Plotando os boxplot
cont_data_plot = sel_cont_vars
#data_canc = data[data['is_canceled'] == 1]
fig, axarr = plt.subplots(5, 2, figsize=(10, 30))
for s in range(0, 5):
sns.boxplot(y=cont_data_plot[s], x='is_canceled', hue='is_canceled', data = data, ax=axarr[s,0])
for s in range(5, len(cont_data_plot)):
sns.boxplot(y=cont_data_plot[s], x='is_canceled', hue='is_canceled', data = data, ax=axarr[s-5,1])
As variaveis com maior diversificação das frequencias são com certeza: 'adr', 'lead_time' e 'days_in_waiting_list'. Ninhumas das variaveis parece assumir normalidade. Nas 3 principais distribuçoes nomeadas acima se observam a presencia de outliers.
A criação de sub-variaveis è feita para segmentar cada variavel categorica selecionada para que podemos ter maior escolha na seleção das variaveis mais importante pelo modelo.
#Criando as variaveis dummies pelas variaveis categoricas
#escluindo "meal", "is_repeated_guest", "reservation_status", "reserved_type_room" e "assigned_type_room"
cat_vars=['hotel','arrival_date_month','market_segment',
'distribution_channel',
'deposit_type','customer_type'
]
for var in cat_vars:
cat_list='var'+'_'+var
cat_list = pd.get_dummies(data[var], prefix=var, dtype=int)
data1=data.join(cat_list)
data=data1
cat_vars=['hotel','arrival_date_month','market_segment',
'distribution_channel',
'deposit_type','customer_type'
]
data_vars=data.columns.values.tolist()
to_keep=[i for i in data_vars if i not in cat_vars]
data_final=data[to_keep]
#Eliminando as variaveis indeseijadas
data_final = data_final.drop(columns=[ #continuous
'reservation_status','reservation_status_date',
'stays_in_week_nights','arrival_date_day_of_month','arrival_date_year',
'reservation_status_date','previous_bookings_not_canceled',
'stays_in_weekend_nights','arrival_date_week_number',
'meal','is_repeated_guest',#categorical
'reserved_room_type','assigned_room_type',
'stays_in_weekend_nights','arrival_date_week_number', #post-rfe
])
print(len(data_final. axes[1]))
#Acrescentando as variaveis dummies categoricas country e agent
data_final = data_final.drop(columns=['country', 'agent'])
data_final = pd.concat([data_final, filtered_data_cou, filtered_data_agn], axis=1) #, filtered_data_cou, filtered_data_agn
print(len(data_final. axes[1]))
#Eliminando as variaveis undefined com meno de 10 dados
data_final = data_final.drop(columns=['market_segment_Undefined',
'distribution_channel_Undefined',
])
print(len(data_final. axes[1]))
data_final.columns.values
47 65 63
array(['is_canceled', 'lead_time', 'adults', 'children', 'babies',
'previous_cancellations', 'booking_changes',
'days_in_waiting_list', 'adr', 'required_car_parking_spaces',
'total_of_special_requests', 'hotel_City Hotel',
'hotel_Resort Hotel', 'arrival_date_month_April',
'arrival_date_month_August', 'arrival_date_month_December',
'arrival_date_month_February', 'arrival_date_month_January',
'arrival_date_month_July', 'arrival_date_month_June',
'arrival_date_month_March', 'arrival_date_month_May',
'arrival_date_month_November', 'arrival_date_month_October',
'arrival_date_month_September', 'market_segment_Aviation',
'market_segment_Complementary', 'market_segment_Corporate',
'market_segment_Direct', 'market_segment_Groups',
'market_segment_Offline TA/TO', 'market_segment_Online TA',
'distribution_channel_Corporate', 'distribution_channel_Direct',
'distribution_channel_GDS', 'distribution_channel_TA/TO',
'deposit_type_No Deposit', 'deposit_type_Non Refund',
'deposit_type_Refundable', 'customer_type_Contract',
'customer_type_Group', 'customer_type_Transient',
'customer_type_Transient-Party', 'country_BRA', 'country_PRT',
'country_ITA', 'country_ESP', 'country_IRL', 'country_BEL',
'country_FRA', 'country_DEU', 'country_GBR', 'country_NLD',
'agent_9.0', 'agent_240.0', 'agent_1.0', 'agent_8.0',
'agent_250.0', 'agent_14.0', 'agent_241.0', 'agent_7.0',
'agent_not_defined', 'agent_28.0'], dtype=object)
Foram escluidas as variaveis categoricas "meal", "reservation_status", "reserved_type_room" e "assigned_type_room" e "is_repeated_guest".
Provavelmente esiste correlação entre as variaveis "is_repeated_guest" e 'previous_bookings_not_canceled' assim como indicava levemente a heatmap.
A escolha de escluir "meal" foi devida ao acreditar que o tipo de refeiçoes reservadas podem não influir no nosso modelo, assim igualmente como as variaveis "reservation_status", "reserved_type_room" e "assigned_type_room".
Nesse passo usaremos o algoritmo SMOTE (Synthetic Minority Oversampling Technique).
O rebalanceamento dos dados da variavel dependente foi devido ao desequilíbrio dos dados entre 0 e 1: percentual de pessoas que não cancelaram 72.51 %; percentual de pessoas que cancelaram 27.49 %.
Foi necessario para evitar de ter desequilibrio entre os erros das pessoes que não cancelaram e os acertos das pessoas que cancelaram.
X = data_final.loc[:, data_final.columns != 'is_canceled']
y = data_final.loc[:, data_final.columns == 'is_canceled']
from imblearn.over_sampling import SMOTE
#Dividindo os dados entre test e treino
os = SMOTE(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
columns = X_train.columns
os_data_X,os_data_y = os.fit_resample(X_train, y_train)
os_data_X = pd.DataFrame(data=os_data_X,columns=columns )
os_data_y = pd.DataFrame(data=os_data_y,columns=['is_canceled'])
#Mostrando os dados do rebalanceamento em percentuais
print("Tamanho do oversampled é: ",len(os_data_X))
print("Número de não cancelamento:",len(os_data_y[os_data_y['is_canceled']==0]))
print("Número de cancelamento:",len(os_data_y[os_data_y['is_canceled']==1]))
print("Proporção de não cancelamento: ",len(os_data_y[os_data_y['is_canceled']==0])/len(os_data_X)*100, "%")
print("Proporção de cancelamento:",
len(os_data_y[os_data_y['is_canceled']==1])/len(os_data_X)*100, "%")
print(len(data_final))
Tamanho do oversampled é: 88870 Número de não cancelamento: 44435 Número de cancelamento: 44435 Proporção de não cancelamento: 50.0 % Proporção de cancelamento: 50.0 % 87396
#Plotando os dados rebalanceados
plt.figure(figsize=(15,8))
figure = sns.countplot(x='is_canceled', data = os_data_y)
Recursive Feature Elimination (RFE) é baseada na ideia de repetidamente construir um modelo e selecionar as variáveis que apresentam as melhores e as piores performance.
O processo é repetido recursivamente até que sejam selecionadas as variáveis que mais importam para o modelo, de modo a ficar com um número parcimonioso de variáveis.
import warnings
#suppress warnings
warnings.filterwarnings('ignore')
#posiçoes das features escluidas
data_final_vars=data_final.columns.values.tolist()
y=['is_canceled']
X=[i for i in data_final_vars if i not in y]
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
rfe = RFE(estimator = LogisticRegression(solver='lbfgs', max_iter=60), n_features_to_select = 55) #, max_features_to_select = 63
rfe = rfe.fit(os_data_X, os_data_y.values.ravel())
print(rfe.support_)
print(rfe.ranking_)
[False True True False True True False False True True True True True True True True True True True True True True True True True False True True True True True True True True True True True False True True True True True True True True True True True True True True True True True False True True True True True True] [4 1 1 3 1 1 7 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 1 1 1 1 1 1 1 1 1 1 1 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1]
#Features escluidas
rfe.ranking = rfe.ranking_.tolist()
cols_v = []
cols_excl = []
for i in rfe.ranking_:
if i > 1:
data_final.columns.values[i]
cols_v.append(i)
for c in cols_v :
v = rfe.ranking.index(c)
cols_excl.append(data_final.columns.values[v])
print(cols_excl)
['is_canceled', 'children', 'booking_changes', 'days_in_waiting_list', 'market_segment_Aviation', 'deposit_type_Non Refund', 'agent_1.0']
#Organização das columnas com as variaveis escluidas pelo RFE
ext_in = data_final.columns.tolist().index('is_canceled')
ext = [data_final.columns.values[ext_in]]
cols = list(set(data_final.columns.values) - set(cols_excl) - set(ext))#
X=os_data_X[cols]
y=os_data_y['is_canceled']
O RFE foi configurado a rodar com 55 features num total de 63, escluindo de fato 7 features (as quais acima) mais a variavel dependente que em qualquer caso não deve ser incluida.
O algoritmo foi rodado varias vezes, pois foi utilizado um abordagem de tipo "passo a passo" no acrescentar e diminuir as variaveis umas por vez para que se pudesse encotrar um modelo com o melhor desepenho entre metricas e acuracia.
A função logit, toma uma probabilidade p como entrada e gera as log-odds dessa probabilidade.
Matematicamente, a função logit é definida como:
$logit(p) = log(p / (1 - p))$
p representa a probabilidade de ocorrência de um evento e logit(p) representa as probabilidades logarítmicas desse evento ocorrer.
As probabilidades logarítmicas podem assumir qualquer valor real de infinito negativo a infinito positivo.
import statsmodels.api as sm
logit_model=sm.Logit(y,X)
result=logit_model.fit(maxiter=100) #maxiter=100
print(result.summary2())
Optimization terminated successfully.
Current function value: 0.357781
Iterations 39
Results: Logit
=======================================================================================================================================================================================================================================================================================================================
Model: Logit Method: MLE
Dependent Variable: is_canceled Pseudo R-squared: 0.484
Date: 2023-07-14 22:38 AIC: 63703.9341
No. Observations: 88870 BIC: 64230.0501
Df Model: 55 Log-Likelihood: -31796.
Df Residuals: 88814 LL-Null: -61600.
Converged: 1.0000 LLR p-value: 0.0000
No. Iterations: 39.0000 Scale: 1.0000
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Coef. Std.Err. z P>|z| [0.025 0.975]
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
country_GBR -1.1200 0.0397 -28.1852 0.0000 -1.1979 -1.0422
country_DEU -1.4155 0.0499 -28.3624 0.0000 -1.5134 -1.3177
country_FRA -1.2138 0.0411 -29.5130 0.0000 -1.2944 -1.1332
arrival_date_month_July -4.0904 0.0653 -62.6727 0.0000 -4.2183 -3.9624
lead_time 0.0085 0.0001 60.5808 0.0000 0.0082 0.0088
hotel_Resort Hotel -0.6255 0.0698 -8.9606 0.0000 -0.7624 -0.4887
customer_type_Transient 1.4612 0.0748 19.5416 0.0000 1.3146 1.6077
country_ITA -0.6961 0.0576 -12.0804 0.0000 -0.8090 -0.5831
previous_cancellations 0.5707 0.0472 12.1013 0.0000 0.4783 0.6632
distribution_channel_TA/TO 2.1083 0.1110 18.9938 0.0000 1.8907 2.3259
customer_type_Group -0.1183 0.2327 -0.5085 0.6111 -0.5744 0.3378
adr 0.0085 0.0003 33.3190 0.0000 0.0080 0.0090
deposit_type_No Deposit 0.9845 0.0666 14.7889 0.0000 0.8540 1.1149
hotel_City Hotel -0.3298 0.0674 -4.8929 0.0000 -0.4620 -0.1977
deposit_type_Refundable 1.2012 0.3670 3.2733 0.0011 0.4820 1.9205
market_segment_Corporate -2.0057 0.1250 -16.0405 0.0000 -2.2507 -1.7606
agent_28.0 -1.0886 0.1397 -7.7900 0.0000 -1.3625 -0.8147
agent_9.0 0.6604 0.0469 14.0710 0.0000 0.5684 0.7524
distribution_channel_GDS 0.9327 0.2907 3.2084 0.0013 0.3629 1.5025
arrival_date_month_January -3.4772 0.0731 -47.5417 0.0000 -3.6205 -3.3338
arrival_date_month_May -3.7405 0.0663 -56.3782 0.0000 -3.8705 -3.6104
country_IRL -1.1205 0.0638 -17.5511 0.0000 -1.2456 -0.9954
country_NLD -1.4457 0.0825 -17.5297 0.0000 -1.6073 -1.2840
arrival_date_month_February -3.3598 0.0692 -48.5854 0.0000 -3.4953 -3.2242
arrival_date_month_September -3.9561 0.0699 -56.5568 0.0000 -4.0932 -3.8190
market_segment_Groups -1.9070 0.1100 -17.3361 0.0000 -2.1226 -1.6914
babies -0.2576 0.1103 -2.3343 0.0196 -0.4738 -0.0413
agent_8.0 -0.7461 0.0933 -7.9934 0.0000 -0.9290 -0.5631
market_segment_Complementary -2.1985 0.1788 -12.2992 0.0000 -2.5488 -1.8482
agent_14.0 -0.0898 0.1063 -0.8443 0.3985 -0.2981 0.1186
arrival_date_month_June -3.9545 0.0673 -58.7784 0.0000 -4.0863 -3.8226
arrival_date_month_March -3.5691 0.0672 -53.1087 0.0000 -3.7008 -3.4374
market_segment_Direct -2.7796 0.1416 -19.6255 0.0000 -3.0572 -2.5021
customer_type_Transient-Party -0.0566 0.0856 -0.6609 0.5087 -0.2244 0.1112
country_ESP -0.5088 0.0433 -11.7553 0.0000 -0.5936 -0.4240
required_car_parking_spaces -367.9017 977324606347738763562705594319887271099121525854230172505446913461384483176448.0000 -0.0000 1.0000 -1915521029646353720571138431757643517946049960555783521165628600361325655228416.0000 1915521029646353720571138431757643517946049960555783521165628600361325655228416.0000
customer_type_Contract 0.5400 0.1017 5.3121 0.0000 0.3408 0.7392
arrival_date_month_December -3.4925 0.0722 -48.4010 0.0000 -3.6340 -3.3511
arrival_date_month_August -4.0027 0.0654 -61.1776 0.0000 -4.1309 -3.8744
arrival_date_month_April -3.5240 0.0662 -53.2332 0.0000 -3.6537 -3.3942
agent_240.0 0.7278 0.0527 13.8082 0.0000 0.6245 0.8311
total_of_special_requests -0.8967 0.0146 -61.5147 0.0000 -0.9253 -0.8682
distribution_channel_Direct 1.7378 0.1461 11.8973 0.0000 1.4515 2.0241
arrival_date_month_November -3.5148 0.0729 -48.2224 0.0000 -3.6576 -3.3719
agent_7.0 -1.4716 0.0799 -18.4088 0.0000 -1.6283 -1.3150
country_BRA -0.4051 0.0710 -5.7038 0.0000 -0.5442 -0.2659
market_segment_Online TA -1.9400 0.1073 -18.0882 0.0000 -2.1502 -1.7298
country_PRT 1.0546 0.0294 35.8161 0.0000 0.9969 1.1123
arrival_date_month_October -3.6188 0.0691 -52.3804 0.0000 -3.7542 -3.4834
market_segment_Offline TA/TO -3.2224 0.1062 -30.3402 0.0000 -3.4306 -3.0143
agent_241.0 -0.9863 0.1067 -9.2439 0.0000 -1.1954 -0.7772
agent_250.0 -0.0477 0.1112 -0.4295 0.6676 -0.2656 0.1701
adults 0.1811 0.0211 8.5833 0.0000 0.1398 0.2225
country_BEL -1.4861 0.0798 -18.6174 0.0000 -1.6425 -1.3296
agent_not_defined -0.4970 0.0720 -6.8981 0.0000 -0.6382 -0.3558
distribution_channel_Corporate 1.0564 0.1351 7.8187 0.0000 0.7916 1.3212
=======================================================================================================================================================================================================================================================================================================================
O valor p associado ao teste LLR é usado para determinar a significância estatística da melhoria no ajuste do modelo ao adicionar os preditores adicionalis.
O valor encontrado do Log-Likelihood è -31796.
Se supoe que a hipótese nula $H_0$ assume que os preditores adicionais não melhoram significativamente o ajuste do modelo.
Se o valor p < 0,05, a melhoria no ajuste é considerada estatisticamente significativa.
No nosso caso, se pode rejeitar a hipótese nula pois o nosso LLR p-value = 0.
O valor da "convergencia" è indicada com 0 ou 1. Dos resultados obtidos se observa que Converged = 1, o que significa que o algoritmo atingiu a convergencia em um ponto em que é improvável que novas iterações melhorem significativamente a solução. No nosso caso foi configurado um numero maximo de iteraçoes = 100, mas so 39 tentativas foram suficientes.
O algoritmo atingiu uma solução estável, ou seja visa encontrar o melhor conjunto de coeficientes que minimizaram a diferença entre as probabilidades previstas e os resultados binários reais do conjunto de dados.
O Pseudo R-squared, consiste na conta do número de previsões corretas e a comparação com o número total de observações. Como o regressando do modelo logit assume valores 0 ou 1, se a probabilidade prevista for maior que 0.5, será classificada como 1, caso contrário, será classificada como 0.
No nosso caso o resultado encontrado foi 0,484, geralmente maior è o valor, maior è a verossimilhança do modelo.
#Calculando as log_odds
log_odds = result.params.values
pd.DataFrame(log_odds,
X.columns,
columns=['coef'])\
.sort_values(by='coef', ascending=False)
| coef | |
|---|---|
| distribution_channel_TA/TO | 2.108303 |
| distribution_channel_Direct | 1.737810 |
| customer_type_Transient | 1.461186 |
| deposit_type_Refundable | 1.201217 |
| distribution_channel_Corporate | 1.056374 |
| country_PRT | 1.054616 |
| deposit_type_No Deposit | 0.984461 |
| distribution_channel_GDS | 0.932744 |
| agent_240.0 | 0.727776 |
| agent_9.0 | 0.660375 |
| previous_cancellations | 0.570742 |
| customer_type_Contract | 0.540004 |
| adults | 0.181112 |
| adr | 0.008547 |
| lead_time | 0.008486 |
| agent_250.0 | -0.047742 |
| customer_type_Transient-Party | -0.056588 |
| agent_14.0 | -0.089750 |
| customer_type_Group | -0.118329 |
| babies | -0.257550 |
| hotel_City Hotel | -0.329839 |
| country_BRA | -0.405051 |
| agent_not_defined | -0.496955 |
| country_ESP | -0.508805 |
| hotel_Resort Hotel | -0.625543 |
| country_ITA | -0.696068 |
| agent_8.0 | -0.746080 |
| total_of_special_requests | -0.896737 |
| agent_241.0 | -0.986266 |
| agent_28.0 | -1.088611 |
| country_GBR | -1.120048 |
| country_IRL | -1.120486 |
| country_FRA | -1.213825 |
| country_DEU | -1.415545 |
| country_NLD | -1.445669 |
| agent_7.0 | -1.471639 |
| country_BEL | -1.486062 |
| market_segment_Groups | -1.907027 |
| market_segment_Online TA | -1.939999 |
| market_segment_Corporate | -2.005674 |
| market_segment_Complementary | -2.198501 |
| market_segment_Direct | -2.779650 |
| market_segment_Offline TA/TO | -3.222448 |
| arrival_date_month_February | -3.359753 |
| arrival_date_month_January | -3.477187 |
| arrival_date_month_December | -3.492538 |
| arrival_date_month_November | -3.514769 |
| arrival_date_month_April | -3.523991 |
| arrival_date_month_March | -3.569124 |
| arrival_date_month_October | -3.618819 |
| arrival_date_month_May | -3.740465 |
| arrival_date_month_June | -3.954469 |
| arrival_date_month_September | -3.956093 |
| arrival_date_month_August | -4.002656 |
| arrival_date_month_July | -4.090368 |
| required_car_parking_spaces | -367.901652 |
#Mostrando os coeficientes de adjuste
odds = np.exp(result.params.values)
pd.DataFrame(odds,
X.columns,
columns=['coef'])\
.sort_values(by='coef', ascending=False)
| coef | |
|---|---|
| distribution_channel_TA/TO | 8.234254e+00 |
| distribution_channel_Direct | 5.684882e+00 |
| customer_type_Transient | 4.311067e+00 |
| deposit_type_Refundable | 3.324160e+00 |
| distribution_channel_Corporate | 2.875924e+00 |
| country_PRT | 2.870872e+00 |
| deposit_type_No Deposit | 2.676369e+00 |
| distribution_channel_GDS | 2.541473e+00 |
| agent_240.0 | 2.070471e+00 |
| agent_9.0 | 1.935518e+00 |
| previous_cancellations | 1.769579e+00 |
| customer_type_Contract | 1.716013e+00 |
| adults | 1.198550e+00 |
| adr | 1.008583e+00 |
| lead_time | 1.008522e+00 |
| agent_250.0 | 9.533800e-01 |
| customer_type_Transient-Party | 9.449833e-01 |
| agent_14.0 | 9.141595e-01 |
| customer_type_Group | 8.884034e-01 |
| babies | 7.729429e-01 |
| hotel_City Hotel | 7.190398e-01 |
| country_BRA | 6.669427e-01 |
| agent_not_defined | 6.083802e-01 |
| country_ESP | 6.012139e-01 |
| hotel_Resort Hotel | 5.349707e-01 |
| country_ITA | 4.985415e-01 |
| agent_8.0 | 4.742221e-01 |
| total_of_special_requests | 4.078986e-01 |
| agent_241.0 | 3.729666e-01 |
| agent_28.0 | 3.366839e-01 |
| country_GBR | 3.262642e-01 |
| country_IRL | 3.261211e-01 |
| country_FRA | 2.970589e-01 |
| country_DEU | 2.427932e-01 |
| country_NLD | 2.355884e-01 |
| agent_7.0 | 2.295489e-01 |
| country_BEL | 2.262619e-01 |
| market_segment_Groups | 1.485213e-01 |
| market_segment_Online TA | 1.437041e-01 |
| market_segment_Corporate | 1.345695e-01 |
| market_segment_Complementary | 1.109693e-01 |
| market_segment_Direct | 6.206024e-02 |
| market_segment_Offline TA/TO | 3.985737e-02 |
| arrival_date_month_February | 3.474382e-02 |
| arrival_date_month_January | 3.089418e-02 |
| arrival_date_month_December | 3.042356e-02 |
| arrival_date_month_November | 2.975467e-02 |
| arrival_date_month_April | 2.948153e-02 |
| arrival_date_month_March | 2.818052e-02 |
| arrival_date_month_October | 2.681432e-02 |
| arrival_date_month_May | 2.374306e-02 |
| arrival_date_month_June | 1.916884e-02 |
| arrival_date_month_September | 1.913774e-02 |
| arrival_date_month_August | 1.826705e-02 |
| arrival_date_month_July | 1.673308e-02 |
| required_car_parking_spaces | 1.668563e-160 |
#Plotando as variaveis com base na importancia
coefficients = pd.DataFrame(odds, X.columns, columns=['coef']).sort_values(by='coef', ascending=True)
plt.figure(figsize=(10,8))
plt.barh(coefficients.index, coefficients['coef'], color=['C{}'.format(i) for i in range(len(coefficients))])
plt.xlabel('Probabilidade (%)')
plt.ylabel('Variáveis')
plt.title('Gráfico de Importância')
plt.show()
Conforme os resultados do grafico de importancia, as sub-variaveis que pertecem ao canal de distribução "distribution_channel" tem uma importancia muito alta pois as 4 sub-variaveis estão nas primeiras 7 colocaçoes.
Nos primeiros 2 lugares se colocam a distribução por meio de tour operator ou tour agency, e por meio de distribução direta.
Tambem o tipo de cliente "customer_type" no especifico "Transient" tem muita importancia;
Faz sentido tambem que o tipo de pagamento tenha influência nos cancelamentos, de fato è posssivel observar que, os pagamentos com rembolsos e os pagamentos sem adiantamento são aqueles com mais probabilidade de cancelamento.
O pais de origem tambem tem muita importancia pois tem um destaque muito forte entre clientes de Portugal comparados com os outros paises.
Os agentes tambem tem uma probabilidade muito variada entre as 10 sub-variaveis. Os agentes com id 240 e 9 são aqueles que mais se destacam dos outros.
E' importante evidenciar como todos os 12 meses de reserva são menos importantes em termos de probabilidade.
Tambem as variaveis "previous_cancellations", "adr", "lead_time", que representam quem ja tinha cancelamentos antecedentes, o custo diario e o tempo de espera entre a reserva e o dia de chegada, tambem tem boa parte da probabilidade.
#Dividindo os dados entre treino e test
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
logreg = LogisticRegression(max_iter=2000)
logreg.fit(X_train, y_train)
LogisticRegression(max_iter=2000)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
LogisticRegression(max_iter=2000)
y_pred = logreg.predict(X_test)
print('Acurácia do Modelo na Base de Teste: {:.2f}'.format(logreg.score(X_test, y_test)))
Acurácia do Modelo na Base de Teste: 0.84
Foi dividida a base de dados entre dados para uso de treino e dados para uso de test, em percentuais de 70% e 30%. O resultado foi que o modelo, fora da amostra, tem acuracia do 84% o que è relativamente bom.
A Matriz de confusão gera os Verdadeiro positivos VP, os Falsos positivos FP e os Falsos negativos FN para calcular as metricas para avaliar a acuracia.
#Gerando a matriz de confusão
from sklearn.metrics import confusion_matrix
confusion_matrix = confusion_matrix(y_test, y_pred)
print(confusion_matrix)
print("Esse modelo nos dá uma relação de",
str(confusion_matrix[0,0]), "+", str(confusion_matrix[1,1]),
"=", confusion_matrix[0,0] + confusion_matrix[1,1], "predições corretas e",
str(confusion_matrix[0,1]), "+", str(confusion_matrix[1,0]),
"=", confusion_matrix[0,1] + confusion_matrix[1,0], "previções erradas")
[[11596 1655] [ 2626 10784]] Esse modelo nos dá uma relação de 11596 + 10784 = 22380 predições corretas e 1655 + 2626 = 4281 previções erradas
VP = 11603
FP = 2634
FN = 10776
Os verdadeiros positivos são os que o modelo afirmou que o resultado era 0 e o resultado real na base de testes era 0 e quando o modelo dizia que era 1 o resultado real da base de testes confirmava o número 1. Os Falsos positivos, são o inverso do explicado anteriormente. Ou seja, o modelo diz que é 1 mas o resultado real diz que é zero.
Usaremos as metricas Precision, Recall e F1-score para ter mais informaçoes sobre a acuracia.
# Calculando as Metricas
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
precision recall f1-score support
0 0.82 0.88 0.84 13251
1 0.87 0.80 0.83 13410
accuracy 0.84 26661
macro avg 0.84 0.84 0.84 26661
weighted avg 0.84 0.84 0.84 26661
.
A Curva ROC è uma metrica visual e analisa a área de uma curva que relaciona a taxa de verdadeiros positivos com falsos positivos. A area entre a linea vermelha e a curva azul (curva da regressao) è a area de acuracia do modelo.
# Plotando a Curva ROC
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
logit_roc_auc = roc_auc_score(y_test, logreg.predict(X_test))
fpr, tpr, thresholds = roc_curve(y_test, logreg.predict_proba(X_test)[:,1])
plt.figure()
plt.figure(figsize=(15,8))
plt.plot(fpr, tpr, label='Regressão Logística (área = %0.2f)' % logit_roc_auc)
plt.plot([0, 1], [0, 1],'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('Taxa de Falso Positivo')
plt.ylabel('Taxa de Verdadeiro Positivo')
plt.title('CURVA ROC')
plt.legend(loc="lower right")
plt.savefig('Log_ROC')
plt.show()
<Figure size 640x480 with 0 Axes>
Com base nos resultados da análise de regressão logística, podemos concluir que a variavel dependente "is_canceled" têm uma influência significativa na probabilidade de realizar um cancelamento. O modelo apresentou uma acurácia de 84%, demonstrando sua capacidade relativamente boa de prever os cancelamentos em uma amostra de dados de teste.
Examinando os coeficientes das variáveis independentes, observamos que o canal de distribução, o tipo de pagamento, o tipo de cliente, o pais de proveniencia, os agentes, valor da diaria, tempo da reserva e cancelamentos antecedentes, demonstraram ser os preditores mais relevantes. Clientes que compraram por meio de todos os canais de distribução sobretudo por meio de agencias e operadores turisticos, clientes que usaram pagamentos não adiantados ou com rembolso, clientes com proveniencia de Portugal apresentaram maior probabilidade de cancelamento.
Além disso, observamos um impacto muito baixo entre a probabilidade de cancelamento, a temporada da reserva e os outros paises europeus excluindo Portugal.
É importante ressaltar que esse modelo de regressão logística tem suas limitações enquanto não foram rodados testes de lineariade nem tecnicas focadas na eliminação ou redução dos outliers, portanto a acuracia do modelo è melhoravel apesar do resultado bom.
Com base nos resultados desta análise, as agencias de turismo e os operadores turisticos, assim como os agentes, podem utilizar essas informações para aprimorar suas estratégias para reter clientes e evitar cancelamentos. Poderiam ser utilizados descontos especiais para clientes portugueses e oferecer vantagens para clientes que tentaram cancelar no passado.